# TODO: Needs better integration with business handler
#       As for now only transactional handlers are supported
from scopt.util import message as msg_util
from . import message

_BUSINESS_HANDLERS = {}


def set_business_handlers(business_handlers):
    global _BUSINESS_HANDLERS
    _BUSINESS_HANDLERS = business_handlers


@message(type='business.compound_request',
         version='0.1',
         reqd=[],
         resd=[])
def handle(data_packet):
    db = msg_util.get_db(data_packet)
    tx_owner = not db.tx_active()
    if tx_owner:
        db.tx_signal()

    responses = []
    response_map = {}
    try:
        for partial_request in msg_util.get_request_data(data_packet):
            partial_request = _update_substitutions(partial_request, responses,
                                                    response_map)

            # TODO: Handle soft exception while processing compound message
            data_packet = _handle_partial_request(data_packet, partial_request)
            responses, response_map = _handle_partial_response(data_packet,
                                                               responses,
                                                               response_map)
        if tx_owner:
            db.tx_commit_all()
    except:
        if tx_owner:
            db.tx_rollback_all()
        raise
    finally:
        if tx_owner:
            db.finish()

    return msg_util.ok_response(data_packet, handle, responses)


def _handle_partial_request(data_packet, partial_request):
    data_packet = msg_util.set_request(data_packet, partial_request)
    handler_id = msg_util.get_handler_id(data_packet)
    business_module = _BUSINESS_HANDLERS[handler_id]
    return business_module(data_packet)


def _handle_partial_response(data_packet, responses, response_map):
    partial_request = msg_util.get_request(data_packet)
    message_id = partial_request['reqh']['message_id']
    partial_response = msg_util.get_response(data_packet)
    partial_response['resh']['message_id'] = message_id
    responses.append(partial_response)
    response_map[message_id] = partial_response
    return (responses, response_map)


def _update_substitutions(partial_request, responses, response_map):
    for k, v in find_substitues(partial_request).items():
        if v.startswith('['):
            value = get_value(responses, v)
        else:
            value = get_value(response_map, v)
        set_value(partial_request, k, value)
    return partial_request


# TODO: Move to utility module
def dict_walker(dic, path='', sep='.'):
    def fp(k):
        return k if len(path) == 0 else sep.join([path, k])

    for k, v in dic.items():
        if isinstance(v, dict):
            for k, v in dict_walker(v, k, sep):
                yield (fp(k), v)
        else:
            yield (fp(k), v)

# TODO: Move to utility module
#def get_value(d, path, sep='.'):
#    return reduce(lambda di, k: di[k], path.split(sep), d)


def norm(token):
    return token if token.startswith('[') else "['" + token + "']"


def parse_q(q):
    return ''.join([norm(token) for token in q.split('.')])


def get_value(o, q):
    s = str(o) + parse_q(q)
    return eval(s)


# TODO: Move to utility module
def set_value(d, path, value, sep='.'):
    keys = path.split(sep)
    reduce(lambda di, k: di[k], keys[:-1], d)[keys[-1]] = value


def find_substitues(d):
    return {k: v.strip()[2:-1]
            for k, v in dict_walker(d)
            if isinstance(v, basestring)
            and v.strip().startswith('${')}
